/*
 * Windy - Level Editor for Isometric Tile-based Games
 * Copyright (C) 2009 Ricardo J. Ricken (Darkl0rd)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include "windy_frame.hpp"
#include "windy_newdoc.hpp"
#include "windy_tilepanel.hpp"
#include "windy_addtileset.hpp"

#include <wx/file.h>
#include <wx/listctrl.h>
#include <wx/tokenzr.h>
#include <wx/dcbuffer.h>

#include <algorithm>
#include <utility>
#include <string>

using namespace std;

#if !defined(__WXMSW__) && !defined(__WXPM__)
#include "../resources/mmoore.xpm"
#endif

BEGIN_EVENT_TABLE(WindyFrame, wxFrame)
   EVT_MENU(Windy_FileNew, WindyFrame::OnFileNew)
   EVT_MENU(Windy_FileClose, WindyFrame::OnFileClose)
   EVT_MENU(Windy_HelpAbout, WindyFrame::OnHelpAbout)
   EVT_CLOSE(WindyFrame::OnClose)
   EVT_COMBOBOX(WdyTilePanel_TlsetBox, WindyFrame::OnChangeTileset)
   EVT_PAINT(WindyFrame::OnPaint)
   EVT_ERASE_BACKGROUND(WindyFrame::OnEraseBackground)
   EVT_SIZE(WindyFrame::OnSize)
   EVT_LEAVE_WINDOW(WindyFrame::OnLeaveWindow)
   EVT_MOTION(WindyFrame::OnMotion)
   EVT_LEFT_DOWN(WindyFrame::OnLeftDown)
   EVT_UPDATE_UI(Windy_FileClose, WindyFrame::OnUpdateUI)
   EVT_UPDATE_UI(Windy_TilePanel, WindyFrame::OnUpdateUI)
   EVT_UPDATE_UI(WdyTilePanel_TlsetBox, WindyFrame::OnUpdateUI)
   

#ifdef _DEBUG
   EVT_MENU(Windy_DebugLogWindow, WindyFrame::OnDebugLogWindow)
   EVT_MENU(Windy_DebugGridRegions, WindyFrame::OnDebugGridRegions)
   EVT_UPDATE_UI(Windy_DebugLogWindow, WindyFrame::OnUpdateUI)
   EVT_UPDATE_UI(Windy_DebugGridRegions, WindyFrame::OnUpdateUI)
#endif //~_DEBUG

   EVT_BUTTON(WdyTilePanel_Add, WindyFrame::OnAddTileset)

END_EVENT_TABLE()


WindyFrame::WindyFrame (const wxString &title/*, MonkeyPrefs &mprefs*/, const wxPoint &pos, const wxSize &size) :
wxFrame(0, wxID_ANY, title, pos, size, wxDEFAULT_FRAME_STYLE | wxNO_FULL_REPAINT_ON_RESIZE | wxCLIP_CHILDREN)/*, prefs(mprefs)*/,
doc(0), grid(0), is_doc_open(false)
{
   SetIcon(wxICON(windy));

#ifdef _DEBUG
   show_grid_regions = false;

#endif //~_DEBUG

   // buffer used in the double buffered frame buffer
   wxSize screen = wxGetDisplaySize();
   buffer = new wxBitmap(screen.x, screen.y);

   // load the interface images (icons and etc.)
   wxImage buttonset(wxT("images/buttons.png"), wxBITMAP_TYPE_PNG);
   const int num_images = buttonset.GetWidth() / 18;

   icons.Create(16, 16, true, num_images);

   for (int i = 0; i < num_images; i++)
      icons.Add(buttonset.GetSubImage(wxRect(i * 16, 0, 16, 16))); 

   // load the grid tiles
   wxImage grid(wxT("images/grid.png"), wxBITMAP_TYPE_PNG);
   grid_tiles.Create(32, 16, true, 3);

   for (int i = 0; i < 3; i++)
      grid_tiles.Add(grid.GetSubImage(wxRect(i * 32, 0, 32, 16)));

#ifdef wxUSE_MENUS
   wxMenuBar *mb = new wxMenuBar();

   wxMenu *file_menu = new wxMenu;
   file_menu->Append(Windy_FileNew, _("New...\tCtrl+N"));
   file_menu->Append(Windy_FileOpen, _("Open...\tCtrl+O"));
   file_menu->Append(Windy_FileClose, _("Close\tCtrl+W"));
   file_menu->Append(Windy_FileSave, _("Save...\tCtrl+S"));
   file_menu->AppendSeparator();
   file_menu->Append(Windy_FileExit, _("Exit\tCtrl+Q"));
   mb->Append(file_menu, _("File"));

#ifdef _DEBUG

   wxMenu *debug_menu = new wxMenu;
   debug_menu->AppendCheckItem(Windy_DebugLogWindow, _("Show Log Window\tCtrl+Shift+L"));
   debug_menu->AppendCheckItem(Windy_DebugGridRegions, _("Show Grid Regions"));
   mb->Append(debug_menu, _("Debug"));

#endif //~_DEBUG

   wxMenu *help_menu = new wxMenu;
   help_menu->Append(Windy_HelpAbout, _("About Windy..."));
   mb->Append(help_menu, _("Help"));

   SetMenuBar(mb);
#endif //~wxUSE_MENUS

   // ---------------------------------
   // -- Window layout ----------------

   wxBoxSizer *global_sizer = new wxBoxSizer(wxHORIZONTAL);
   tp = new TilePanel(this, Windy_TilePanel, icons);
   global_sizer->Add(tp, wxSizerFlags().Expand());
   global_sizer->AddStretchSpacer(1);



   SetSizer(global_sizer);

   // TEMP ------
   WindyDocument *temp = new WindyDocument(wxT("Teste 1"), wxSize(10, 10));
   DoNewDocument(temp);

#ifdef _DEBUG
   //wxCommandEvent evt;
   //evt.SetInt(1);

   //OnDebugGridRegions(evt);
#endif

   // -----------

   // logger
   logger = new wxLogWindow(this, wxT("Logger"), false);
   wxLog::SetActiveTarget(logger);
   
   // restores previous UI state
   /*
   if (prefs.getBool(wxT("remember-ui-state")))
   {
      int stypeid = prefs.getBool(wxT("search-type"), wxT("rs")) ? MonkeyMoore_RelativeSearch : MonkeyMoore_ValueScanSearch;
      GetWindow <wxRadioButton *> (stypeid)->SetValue(true);

      OnSearchType(wxCommandEvent(wxEVT_COMMAND_RADIOBUTTON_SELECTED, stypeid));

      wildcard->SetValue(wxString::Format(wxT("%c"), prefs.get(wxT("wildcard")).GetChar(0)));
      show_all->SetValue(prefs.getBool(wxT("show-all-results")));

      if (prefs.getBool(wxT("advanced-shown")))
         OnAdvanced(wxCommandEvent());
   }*/
}

/**
* Destructor. Frees allocated memory and resources.
*/
WindyFrame::~WindyFrame ()
{

   if (is_doc_open) delete doc;
   if (grid) delete grid;
}

void WindyFrame::OnFileNew (wxCommandEvent &WXUNUSED(event))
{
   WindyNewDocDialog d(this, _("Create a new level"));
   
   if (d.ShowModal() == wxID_OK)
   {
      DoCloseDocument();

      wxSize gridsz = d.GetGridSize();
      wxString name = d.GetDocName();

      WindyDocument *newdoc = new WindyDocument(name, gridsz);
      DoNewDocument(newdoc);
   }
}

void WindyFrame::OnFileClose (wxCommandEvent &event)
{
   DoCloseDocument();

   Refresh(), Update();
}

#ifdef _DEBUG

void WindyFrame::OnDebugLogWindow (wxCommandEvent &event)
{
   if (event.IsChecked())
   {
      wxSize display_sz = wxGetDisplaySize();
      wxSize logger_sz = logger->GetFrame()->GetSize();

      logger->GetFrame()->SetPosition(wxPoint(display_sz.x - logger_sz.x, display_sz.y - logger_sz.y - 30));
      logger->Show();
   }
   else
      logger->Show(false);
}

void WindyFrame::OnDebugGridRegions (wxCommandEvent &event)
{
   show_grid_regions = event.IsChecked();
   Refresh(), Update();
}

#endif //~_DEBUG

/**
* This method is called when the about button is pressed.
* @param event not used
*/
void WindyFrame::OnHelpAbout (wxCommandEvent &WXUNUSED(event))
{
   //MonkeyAbout about(this, _("About Monkey-Moore"), wxSize(370, 300));
   //about.ShowModal();
}

void WindyFrame::OnAddTileset (wxCommandEvent &event)
{
   WindyAddTileset d(this, _("Add tileset"), icons, wxSize(360, 400));

   if (d.ShowModal() == wxID_OK)
   {
      WindyTileset ts(d.getFilename(), d.getName(), d.getDescription(), d.getTileset());
      int index = doc->addTileset(ts);

      wxComboBox *cb = dynamic_cast <wxComboBox *>(FindWindowById(WdyTilePanel_TlsetBox));
      int pos = cb->Append(ts.name, (void *)index);

      if (cb->GetSelection() == wxNOT_FOUND)
      {
         cb->SetSelection(pos);
         tp->changeTileset(ts);
      }
   }
}

void WindyFrame::OnChangeTileset(wxCommandEvent &event)
{
   wxComboBox *cb = GetWindow <wxComboBox *>(WdyTilePanel_TlsetBox);

   int sel = cb->GetCurrentSelection();
   WindyTileset &ts = doc->getTileset((int)cb->GetClientData(sel));

   tp->changeTileset(ts);
}

void WindyFrame::OnUpdateUI (wxUpdateUIEvent &event)
{
   switch (event.GetId())
   {
      case Windy_FileClose:
         event.Enable(is_doc_open);
         break;

      case Windy_TilePanel:
         dynamic_cast <wxPanel *> (event.GetEventObject())->Show(is_doc_open);
         break;

      case WdyTilePanel_TlsetBox:
         event.Enable(doc->numTilesets() > 0);
         break;

#ifdef _DEBUG
      case Windy_DebugLogWindow:
         event.Check(logger->GetFrame()->IsShown());
         break;

      case Windy_DebugGridRegions:
         event.Check(show_grid_regions);
         break;
#endif
   }

}


/**
* Method called when the window is about to be closed.
* @param event not used
*/
void WindyFrame::OnClose (wxCloseEvent &WXUNUSED(event))
{
   /*
   if (prefs.getBool(wxT("remember-ui-state")))
   {
      prefs.set(wxT("search-type"), GetWindow <wxRadioButton *> (MonkeyMoore_RelativeSearch)->GetValue() ? wxT("rs") : wxT("vsr"));
      prefs.set(wxT("wildcard"), GetWindow <wxTextCtrl *> (MonkeyMoore_Wildcard)->GetValue().substr(0, 1));
      prefs.setBool(wxT("advanced-shown"), advanced_shown);
      prefs.setBool(wxT("show-all-results"), GetWindow <wxCheckBox *> (MonkeyMoore_AllResults)->IsChecked());

      prefs.setBool(wxT("window-maximized"), IsMaximized());
   }

   if (!IsMaximized() && prefs.getBool(wxT("remember-size")))
      prefs.setSize(wxT("window-size-x"), wxT("window-size-y"), GetSize());

   if (!prefs.getBool(wxT("window-centered")) && prefs.getBool(wxT("remember-position")))
      prefs.setPoint(wxT("window-position-x"), wxT("window-position-y"), GetPosition());
   */

   DoCloseDocument();
   Destroy();
}

void WindyFrame::OnMotion (wxMouseEvent &event)
{
   wxPoint pos = event.GetPosition();

   if (is_doc_open)
   {
      if (grid->b_box.Contains(pos))
      {
         pos = pos - grid->b_box.GetPosition();
         wxPoint col = MousePosToGridCol(pos);

         GridRect &cur = grid->map[col.y * grid->map_len + col.x];
         bool on_bottom = CursorOnBottom(MousePosToGridPos(pos), cur.orient);

#ifdef _DEBUG
         if (show_grid_regions && col != over_grid_col)
         {
            over_grid_col = col;
            Refresh(), Update();
         }

         //wxLogStatus("[i] Mouse is over grid column [%d, %d]", over_grid_col.x, over_grid_col.y);
         //wxLogStatus("    Mouse is over the %s half of the region", on_bottom ? "lower" : "upper");
#endif

         int tile = on_bottom ? cur.lowerTile : cur.upperTile;
         wxPoint tile_pos(tile % doc->width(), tile / doc->width());

         if (!grid->contains(tile_pos))
            tile_pos = wxPoint(-1, -1);

         if (tile_pos != grid->over)
         {
            wxLogStatus("[i] Mouse is now over tile [%d,%d]", tile_pos.x, tile_pos.y);
            grid->over = tile_pos;

            Refresh(), Update();
         }

         if (!grid->mouse_over)
         {
            grid->mouse_over = true;
            wxLogStatus(wxT("[i] Mouse is over the grid"));

            //Refresh(), Update();
         }
      }
      else
      {
         if (grid->mouse_over)
         {
            grid->mouse_over = false;
            grid->over = wxPoint(-1, -1);
            wxLogStatus(wxT("[i] Mouse ain't over the grid anymore"));
            Refresh(), Update();
         }

         grid->mouse_over = false;
      }
   }
}

void WindyFrame::OnLeftDown (wxMouseEvent &event)
{
   wxPoint pos = event.GetPosition();
   
   if (is_doc_open)
   {
      if (grid->b_box.Contains(pos))
      {
         pos = pos - grid->b_box.GetPosition();
         wxPoint col(pos.x / 16, pos.y / 8);

         GridRect &cur = grid->map[col.y * grid->map_len + col.x];
         bool on_bottom = CursorOnBottom(MousePosToGridPos(pos), cur.orient);

         int tile = on_bottom ? cur.lowerTile : cur.upperTile;
         wxPoint tile_pos(tile % doc->width(), tile / doc->width());

         if (tile_pos != grid->sel)
         {
            wxLogStatus("[i] A new tile was selected: [%d,%d]", tile_pos.x, tile_pos.y);

            grid->sel = grid->contains(tile_pos) ? tile_pos : grid->invalid_pos;
            Refresh(), Update();
         }
      }
      else
      {
         grid->sel = grid->invalid_pos;
         Refresh(), Update();
      }
   }
}

void WindyFrame::OnLeaveWindow (wxMouseEvent &event)
{
   if (is_doc_open)
   {
      grid->mouse_over = false;
      Refresh(), Update();
   }
}

void WindyFrame::OnPaint (wxPaintEvent &event)
{
   wxBufferedPaintDC bdc(this, *buffer);

   bdc.SetBrush(*wxWHITE_BRUSH);
   bdc.Clear();

   if (is_doc_open)
   {
      wxSize client_sz = GetClientSize();
      wxSize finalsz(grid->map_len * 16, grid->map_len * 8);
      wxPoint center(client_sz.x / 2, client_sz.y / 2);
      grid->b_box = wxRect(center.x - finalsz.x / 2, center.y - finalsz.y / 2, finalsz.x, finalsz.y);
      grid->b_box.x += tp->GetSize().x / 2;

      wxBitmap tile = grid_tiles.GetBitmap(WindyGrid_Regular);
      wxBitmap over = grid_tiles.GetBitmap(WindyGrid_Over);
      wxBitmap sel = grid_tiles.GetBitmap(WindyGrid_Selected);

      const wxSize &docsize = doc->size();

      int extraspacing = docsize.x > docsize.y ?
         std::min(docsize.x, docsize.y) :
         std::max(docsize.x, docsize.y);

      wxSize start(grid->b_box.x + (extraspacing - 1) * 16, grid->b_box.y);

      for (int y = 0; y < doc->height(); y++)
         for (int x = 0; x < doc->width(); x++)
            bdc.DrawBitmap(tile, start.x + 16 * (x - y), start.y + 8 * (x + y), true);


#ifdef _DEBUG
      if (show_grid_regions)
      {
         bdc.SetPen(*wxGREY_PEN);

         for (int i = 0; i <= grid->map_len; i++)
         {
            bdc.DrawLine(grid->b_box.x, grid->b_box.y + i * 8, grid->b_box.x + grid->b_box.width, grid->b_box.y + i * 8);
            bdc.DrawLine(grid->b_box.x + i * 16, grid->b_box.y, grid->b_box.x + i * 16, grid->b_box.y + grid->b_box.height);
         }

         if (grid->mouse_over)
         {
            bdc.SetPen(*wxCYAN_PEN);
            bdc.SetBrush(*wxTRANSPARENT_BRUSH);
            bdc.DrawRectangle(grid->b_box.x + over_grid_col.x * 16, grid->b_box.y + over_grid_col.y * 8, 17, 9);
         }
      }
#endif

      if (grid->sel != grid->invalid_pos)
         bdc.DrawBitmap(sel, start.x + 16 * (grid->sel.x - grid->sel.y), start.y + 8 * (grid->sel.x + grid->sel.y), true);

      if (grid->over != grid->invalid_pos)
         bdc.DrawBitmap(over, start.x + 16 * (grid->over.x - grid->over.y), start.y + 8 * (grid->over.x + grid->over.y), true);
   }
}


void WindyFrame::OnEraseBackground (wxEraseEvent &WXUNUSED(event))
{
   // does nothing on purpose, just to prevent flickering
}

void WindyFrame::OnSize (wxSizeEvent &event)
{
   wxFrame::OnSize(event);

   Refresh(), Update();
}

void WindyFrame::DoCloseDocument ()
{
   if (is_doc_open)
   {
      delete doc;
      delete grid;

      doc = 0;
      grid = 0;
      is_doc_open = false;

      SetTitle(getBaseTitle());
   }
}

void WindyFrame::DoNewDocument (WindyDocument *newDoc)
{
   if (newDoc)
   {
      doc = newDoc;
      grid = new IsometricGrid(*doc);
      tp->Show();
      is_doc_open = true;

      SetTitle(wxString::Format(wxT("%s - %s [%d][%d]"), getBaseTitle(), doc->name(), doc->width(), doc->height()));
      Refresh(), Update();
   }
}

